# -*- coding: utf-8 -*-
import random
import subprocess
import time
import logging
import argparse

from appium import webdriver
# from appium.options.android import UiAutomator2Options
from datetime import datetime
import requests
import base64
from io import BytesIO
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException
import numpy as np
import cv2
import pytesseract
from PIL import Image

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logger = logging

APP = "XHS"

app_package_map = {
    "XHS": ("com.xingin.xhs", ".index.v2.IndexActivityV2")
}


class XHSAppiumOperate:
    def __init__(self, **kwargs):
        # 用于记录已评论的内容
        self.screen_width = None
        self.screen_height = None
        self.targetUrl = kwargs.get("targetUrl").replace("\\n", "\n")
        self.text = kwargs.get("commit_list").replace("\\n", "\n")
        self.deviceName = kwargs.get("deviceName")
        self.appium_url = kwargs.get("appium_url")
        self.accountId = kwargs.get("accountId", "0000")
        self.platformVersion = kwargs.get("platformVersion")
        self.systemPort = kwargs.get("systemPort")
        self.adbPort = kwargs.get("adbPort")
        self.min_interval = int(kwargs.get("min_interval").replace("[", "").replace("]", ""))  # 默认最小间隔30秒
        self.max_interval = int(kwargs.get("max_interval").replace("[", "").replace("]", ""))  # 默认最大间隔60秒
        self.job_id = kwargs.get("job_id")
        self.comment_input_location = None  # 添加这行来保存评论输入框位置

        self.driver = None
        self.max_retries = 1
        self.failure_count = 0

    def run(self):
        while self.max_retries > 0:
            try:
                if self.driver:
                    self.driver.quit()
                self.connection()
                logger.info(f"设备 {self.deviceName} 连接成功")
                time.sleep(3)

                logger.info("开始执行小红书评论任务...")
                self.script()

                break
            except Exception as e:
                logger.error(f"发生异常: {e}")
                self.failure_count += 1
                self.max_retries -= 1
                if self.max_retries == 0:
                    self.normal_end()
                    raise Exception("任务失败")
        logger.info("任务结束")

    def upload_screenshot(self, fileName):
        """
        将当前屏幕截图上传至指定接口
        :param job_id: rpaTaskid，即任务ID
        """
        try:
            # 1. 获取截图的 Base64 字符串
            screenshot_base64 = self.driver.get_screenshot_as_base64()

            if not screenshot_base64:
                logger.error("截图失败，无法获取 Base64 数据")
                return False

            # 2. 构造文件数据（模拟 multipart/form-data）
            # 使用指定的文件名格式
            files = {
                'files': [
                    {
                        "file": screenshot_base64,
                        "fileName": f"{fileName}"
                    }
                ]
            }

            # 3. 发送 POST 请求
            url = f"http://172.25.1.30:8889/social/device/task/upload/{self.job_id}"

            response = requests.post(url, json=files)

            if response.status_code == 200:
                logger.info(f"截图上传成功: {response.json()}")
            else:
                logger.error(f"截图上传失败，状态码: {response.status_code}, 响应: {response.text}")

        except Exception as e:
            logger.error(f"上传截图时发生异常: {e}")

    @staticmethod
    def execute_adb_command(adb_command):
        """
        执行单条 ADB 命令

        :param adb_command: 完整的 ADB 命令字符串
        :return: 命令执行结果（stdout）
        """
        try:
            logger.info(f"执行 ADB 命令: {adb_command}")
            result = subprocess.run(
                adb_command,
                shell=True,
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            logger.info(f"命令执行成功: {adb_command}")
            return result.stdout.decode('utf-8')
        except subprocess.CalledProcessError as e:
            logger.error(f"命令执行失败: {adb_command}")
            logger.error(f"错误信息: {e.stderr.decode('utf-8')}")

    def initialize_device_settings(self):
        """
        执行设备初始化所需的 ADB 命令
        """
        logger.info("开始初始化设备设置")

        commands = [
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global adb_keep_usb_enabled 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global miui_optimization 0",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global tcp_keepalive 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell dumpsys battery unplug",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global background_process_limit 5",
            f"adb -s {self.deviceName} -P {self.adbPort} shell am set-standby-bucket io.appium.uiautomator2.server ACTIVE",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm trim-caches 500M",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell input keyevent KEYCODE_CLEAR"
        ]

        for cmd in commands:
            try:
                self.execute_adb_command(cmd)
            except Exception as e:
                logger.error(f"执行命令失败: {cmd}")
                logger.error(f"错误详情: {e}")

    def connection(self):
        self.initialize_device_settings()
        desired_caps = {
            "platformName": "Android",
            "platformVersion": self.platformVersion,
            "deviceName": self.deviceName,
            "udid": self.deviceName,
            "automationName": "UiAutomator2",
            "appPackage": app_package_map[APP][0],
            "appActivity": app_package_map[APP][1],
            "noReset": True,
            "fullReset": False,
            "systemPort": int(self.systemPort),
            "adbPort": int(self.adbPort),
            "appWaitForLaunch": True,
            "noSign": True,
            "autoLaunch": True,
            "newCommandTimeout": 1000,
            "skipLogcatCapture": True,
            "disableWindowAnimation": True,
            "disableAndroidWatchers": True,
            "adbExecTimeout": 30000,
            "uiautomator2ServerInstallTimeout": 60000,
            "skipDeviceInitialization": True,
        }
        self.driver = webdriver.Remote(self.appium_url, desired_caps)
        # self.driver = webdriver.Remote(self.appium_url, options=UiAutomator2Options().load_capabilities(desired_caps))
        self.screen_width, self.screen_height = self.get_window_size()

    def script(self):
        """
        主脚本流程，处理多个链接并进行重试机制
        """
        logger.info("开始执行小红书评论任务")

        # 按行分割 targetUrlList
        target_url = self.targetUrl

        # 提取所有有效的链接
        line = target_url.strip()
        if line:  # 忽略空行
            target_url = self.extract_xhs_url(line)

        # 处理每个链接，包含重试机制
        logger.info(f"开始处理链接: {target_url}")

        try:
            # 执行单个链接的处理流程
            self.process_single_url(target_url)
            logger.info(f"链接 {target_url} 处理成功")
        except Exception as e:
            logger.error(f"处理链接 {target_url} 时发生异常: {e}")
        self.normal_end()
        logger.info("所有链接处理完成")

    def process_single_url(self, targetUrl):
        logger.info("开始执行短链评论任务")
        # 1. 打开链接
        try:
            # self.open_xhs_directly(targetUrl)
            self.open_link_in_browser(targetUrl)
            # time.sleep(20)  # 等待页面加载

            # 2. 点击「在小红书打开」按钮
            screenshot_data = self.driver.get_screenshot_as_png()
            try:
                xhs_button = WebDriverWait(self.driver, 10).until(
                    EC.presence_of_element_located(
                        (AppiumBy.XPATH, '//android.widget.TextView[contains(@text, "打开")]'))
                )
                xhs_button.click()
            except Exception as e:
                logger.error(f"未找到「在小红书打开」按钮")

                result = self.find_basic_data_position_from_bytes(screenshot_data, text='打开')
                x, y, w, h = result
                logger.info(f"使用文字识别定位,打开按钮位置: {x}, {y}, {w}, {h}")
                # 点击「内打开」按钮
                self.tap(self.screen_width / 2, y + h // 2)

        except Exception as e:
            logger.error(f"打开链接出现问题，可能自动跳转，看之后的操作是否可以继续")

        # 3. 等待页面加载完成
        time.sleep(1)
        # 4. 随机选择一个评论并发送
        # 将评论内容按换行符分割成列表
        comment_options = self.text.split('\n')
        # # 过滤掉空行
        comment_options = [comment.strip() for comment in comment_options if comment.strip()]
        #
        # # 随机选择一个评论
        # # if comment_options:
        # #     selected_comment = random.choice(comment_options)
        # #     logger.info(f"准备评论内容：{selected_comment}")

        if not comment_options:
            logger.error("没有可用的评论内容")
            return

            # 循环发送评论
        for i, comment in enumerate(comment_options):
            try:
                # 如果不是最后一条评论，则等待随机间隔时间
                wait_time = random.randint(self.min_interval, self.max_interval)
                logger.info(f"等待 {wait_time} 秒后发送评论")
                time.sleep(wait_time)

                logger.info(f"发送第 {i + 1} 条评论: {comment}")
                self.leave_comment(comment)


            except Exception as e:
                logger.error(f"发送评论 '{comment}' 失败: {e}")
                continue

        # 5. 结束本次操作
        self.normal_end()
        time.sleep(5)

    def leave_comment(self, comment_text):
        """
        在当前视频或者笔记页面输入评论内容并提交
        :param comment_text: 要评论的内容
        """

        try:
            logger.info("已经进入直播间，开始进行评论操作")
            # 检查是否已保存评论输入框位置
            if self.comment_input_location:
                # 直接点击已保存的位置
                x, y = self.comment_input_location
                self.tap(x, y)
                logger.info(f"使用已保存的位置点击评论输入框: ({x}, {y})")
                time.sleep(3)
            else:
                # 首次查找并保存位置
                try:
                    comment_input = self.find_element_with_retry(
                        by=AppiumBy.ANDROID_UIAUTOMATOR,
                        value='new UiSelector().textContains("说点什么")',
                        timeout=5
                    )
                except Exception as e:
                    logger.error(f"未找到输入框: {e}")
                    comment_input = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[@content-desc="评论输入框"]',
                        timeout=5
                    )

                if comment_input:
                    # 保存元素位置
                    location = comment_input.location
                    size = comment_input.size
                    # 计算元素中心点坐标
                    center_x = location['x'] + size['width'] // 2
                    center_y = location['y'] + size['height'] // 2
                    self.comment_input_location = (center_x, center_y)

                    # 使用坐标点击而不是元素点击
                    self.tap(center_x, center_y)
                    logger.info(f"找到评论输入框，使用位置点击并保存坐标: {self.comment_input_location}")
                    time.sleep(3)
                else:
                    raise Exception("未找到输入框")

            # 输入评论内容
            input_box = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.EditText',
                timeout=5
            )

            input_box.send_keys(str(comment_text))
            time.sleep(5)

            # 点击发送按钮（替换为你实际的控件 ID）
            send_button = self.find_element_with_retry(
                by=AppiumBy.ANDROID_UIAUTOMATOR,
                value='new UiSelector().text("发送")',
                timeout=5
            )
            send_button.click()
            logger.info("评论成功！")

            self.upload_screenshot(f"直播间互动_{comment_text}")
        except Exception as e:
            raise e
        time.sleep(5)

    def base64_to_img(self, base64_string):
        """将 base64 字符串转为 OpenCV 图像"""
        img_data = base64.b64decode(base64_string)
        nparr = np.frombuffer(img_data, np.uint8)
        img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        return img

    def tap(self, x, y):
        """点击屏幕指定坐标"""
        self.driver.tap([(x, y)], 500)

    def take_screenshot(self) -> np.ndarray:
        """ADB截屏并转为OpenCV格式"""
        screenshot_path = '/tmp/screenshot.png'
        # 通过ADB截屏保存为临时文件
        # subprocess.run("adb exec-out screencap -p > screen.png", shell=True)
        import os
        if os.path.exists(screenshot_path):
            os.remove(screenshot_path)

        # 保存截图（会自动覆盖同名文件）
        self.driver.save_screenshot(screenshot_path)
        # 读取截图
        return cv2.imread("/tmp/screenshot.png")

    def extract_xhs_url(self, url_text):
        """
        从文本中提取小红书链接
        :param url_text: 包含链接的文本
        :return: 提取到的链接或原始链接
        """
        import re

        # 检查是否是正常的URL格式
        if url_text.startswith(('http://', 'https://')):
            return url_text

        # 如果是包含口令信息的文本，尝试提取其中的链接
        # 匹配小红书的各种链接格式:
        # 1. http://xhslink.com/...
        # 2. https://www.xiaohongshu.com/...
        url_patterns = [
            r'http[s]?://xhslink\.com/[^\s]+',
            r'http[s]?://www\.xiaohongshu\.com/[^\s]+'
        ]

        for pattern in url_patterns:
            match = re.search(pattern, url_text)
            if match:
                extracted_url = match.group(0)
                logger.info(f"从文本中提取到链接: {extracted_url}")
                return extracted_url

        # 如果没有找到链接，返回原始文本
        logger.warning(f"未在文本中找到链接，使用原始文本: {url_text}")
        return ''

    def find_basic_data_position_from_bytes(self, screenshot_data: bytes, text="基础数据"):
        """
        从字节数据中查找"基础数据"文字的位置信息
        :param screenshot_data: 屏幕截图的字节数据
        :return: "基础数据"文字的位置信息(x, y, width, height)或None
        """
        try:
            # 将字节数据转换为OpenCV图像
            nparr = np.frombuffer(screenshot_data, np.uint8)
            img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

            if img is None:
                raise ValueError("无法解码图像数据")

            # 转为 PIL 图像格式
            pil_img = Image.fromarray(img)
            # Linux下通常不需要指定tesseract路径，但如果需要可以取消下面一行的注释并设置正确的路径
            # pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'
            # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
            # 使用 OCR 识别文字（支持中文）并获取位置信息
            # 使用页面分割模式6来更好地识别连续文本
            data = pytesseract.image_to_data(pil_img, lang='chi_sim+eng', output_type=pytesseract.Output.DICT,
                                             config='--psm 6')

            # 合并同一行上的文本以更好地识别连续文本
            lines = self.merge_text_lines(data)

            # 在合并后的行中查找目标文本"基础数据"
            for i, line in enumerate(lines):
                # 移除文本中的所有空格后再查找
                cleaned_text = ''.join(line['text'].split())
                # 只要包含"基础数据"就匹配
                if text in cleaned_text:
                    x, y, w, h = line['x'], line['y'], line['w'], line['h']
                    print(f"找到文字'{text}': (x={x}, y={y}, width={w}, height={h})")
                    return x, y, w, h

            print(f"未找到'{text}'")
            return None
        except Exception as e:
            print(f"OCR 识别失败: {e}")
            return None

    def merge_text_lines(self, data, y_tolerance=10):
        """
        合并同一行上的文本框

        :param data: pytesseract 返回的数据字典
        :param y_tolerance: Y坐标容差，用于判断是否在同一行
        :return: 合并后的行列表
        """
        lines = []
        n_boxes = len(data['level'])

        # 提取有效的文本框
        boxes = []
        for i in range(n_boxes):
            text = data['text'][i].strip()
            # 去除空字符串但仍显示空白字符
            if len(text) > 0:
                box = {
                    'text': text,
                    'x': data['left'][i],
                    'y': data['top'][i],
                    'w': data['width'][i],
                    'h': data['height'][i]
                }
                boxes.append(box)

        # 根据Y坐标对文本框进行分组
        if not boxes:
            return lines

        # 按Y坐标排序
        boxes.sort(key=lambda b: b['y'])

        # 分组同一行的文本框
        current_line = [boxes[0]]
        for i in range(1, len(boxes)):
            # 如果当前文本框与前一个文本框在同一行，则添加到当前行
            if abs(boxes[i]['y'] - boxes[i - 1]['y']) <= y_tolerance:
                current_line.append(boxes[i])
            else:
                # 否则，结束当前行并将文本框按X坐标排序后合并
                current_line.sort(key=lambda b: b['x'])
                merged_text = ' '.join([b['text'] for b in current_line])

                # 计算合并后的边界框
                min_x = min([b['x'] for b in current_line])
                max_x = max([b['x'] + b['w'] for b in current_line])
                min_y = min([b['y'] for b in current_line])
                max_y = max([b['y'] + b['h'] for b in current_line])

                lines.append({
                    'text': merged_text,
                    'x': min_x,
                    'y': min_y,
                    'w': max_x - min_x,
                    'h': max_y - min_y
                })

                # 开始新行
                current_line = [boxes[i]]

        # 处理最后一行
        if current_line:
            current_line.sort(key=lambda b: b['x'])
            merged_text = ' '.join([b['text'] for b in current_line])

            # 计算合并后的边界框
            min_x = min([b['x'] for b in current_line])
            max_x = max([b['x'] + b['w'] for b in current_line])
            min_y = min([b['y'] for b in current_line])
            max_y = max([b['y'] + b['h'] for b in current_line])

            lines.append({
                'text': merged_text,
                'x': min_x,
                'y': min_y,
                'w': max_x - min_x,
                'h': max_y - min_y
            })

        return lines

    def open_link_in_browser(self, url):
        """
        使用设备默认浏览器打开指定链接
        :param url: 需要打开的网页链接
        """
        logger.info(f"尝试使用默认浏览器打开链接: {url}")
        try:
            # 使用 adb 命令启动浏览器并访问链接
            subprocess.run([
                'adb', '-s', self.deviceName,
                '-P', self.adbPort,
                'shell', 'am', 'start',
                '-a', 'android.intent.action.VIEW',
                '-d', url
            ], check=True)

            # 等待弹窗出现
            time.sleep(5)
            logger.info("设置检查是否未设置的打开方式")
            # 尝试查找并点击Chrome浏览器选项
            try:
                chrome_button = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.TextView[contains(@text, "Chrome")]',
                    timeout=5
                )
                if chrome_button:
                    chrome_button.click()
                    logger.info("已选择Chrome浏览器")

                    # 等待"始终"按钮出现并点击
                    time.sleep(2)
                    always_button = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[contains(@text, "始终")]',
                        timeout=5
                    )
                    if always_button:
                        always_button.click()
                        logger.info("已点击'始终'按钮")
            except Exception as e:
                logger.warning(f"处理浏览器选择弹窗时出错: {e}")

        except Exception as e:
            logger.error(f"打开链接失败: {e}")
            raise

    def swipe_to_next(self):
        # 向上滑动
        self.driver.swipe(
            self.screen_width / 2,
            self.screen_height * 0.6,
            self.screen_width / 2,
            self.screen_height * 0.2,
            100
        )

    def find_element_with_retry(self, by, value, timeout=5):
        max_retries = 2
        for attempt in range(max_retries + 1):
            try:
                element = WebDriverWait(self.driver, timeout).until(
                    EC.presence_of_element_located((by, value))
                )
                return element
            except Exception as e:
                if attempt < max_retries:
                    logger.error(f"获取元素失败，重试")
                    time.sleep(1)  # 等待1秒后重试
                else:
                    logger.error(f"获取元素失败，超过3次")
                    logger.error(f"元素名称: {value}")
                    raise NoSuchElementException(f"找不到元素: {value}")

    def get_window_size(self):
        size = self.driver.get_window_size()
        return size['width'], size['height']

    def normal_end(self):
        logger.info("返回首页并退出")
        try:
            self.driver.press_keycode(3)  # HOME 键
            self.driver.quit()
        except:
            pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-j', '--job_id', dest='job_id', type=str, default=None, required=True, help='target job_id')
    parser.add_argument('-u', '--appium_url', dest='appium_url', type=str, default=None, help='target appium_url')
    parser.add_argument('-d', '--deviceName', dest='deviceName', type=str, default=None, help='target deviceName')
    parser.add_argument('-s', '--systemPort', dest='systemPort', type=str, default=None, help='target systemPort')
    parser.add_argument('-a', '--adbPort', dest='adbPort', type=str, default=None, help='target adbPort')
    parser.add_argument('-p', '--platformName', dest='platformName', type=str, default=None, help='target platformName')
    parser.add_argument('-ver', '--platformVersion', dest='platformVersion', type=str, default=None,
                        help='target platformVersion')
    parser.add_argument('-v', '--variables', dest='variables', type=str, default='{}', help='target variables')
    parser.add_argument('-accountId', '--accountId', dest='accountId', type=str, default="000", help='target accountId')
    parser.add_argument('-targetUrl', '--targetUrl', type=str, required=True)
    parser.add_argument('-commit_list', '--commit_list', type=str, required=True, default="真棒\n非常棒\nGood")
    parser.add_argument('-min_interval', '--min_interval', type=str, default="30", help='最小评论间隔时间(秒)')
    parser.add_argument('-max_interval', '--max_interval', type=str, default="60", help='最大评论间隔时间(秒)')
    args = parser.parse_args()
    XHSAppiumOperate(**vars(args)).run()
